#include "pipe.hpp"

#define PROCESSNUM 10
#define PIPEFDSIZE 2

// 子进程进行任务处理
void Worker() {
    while(true) {
        int TaskCode = 0;
        ssize_t n = read(0, &TaskCode, sizeof TaskCode);
        if(n == sizeof TaskCode) {
            cout << "child process says that : mypid = " << getpid() << " TeskCode = " << 
            " TaskCode = " << TaskCode << endl;
            vtasks[TaskCode]();
        }
        else {
            break;
        }
    }
}

void InitProcessPool(priority_queue<chunnels, vector<chunnels>, typename chunnels::mycmp>* const ppc) {
    for(int i = 0; i < PROCESSNUM; ++i) {
        // 创建管道文件
        // fds[0] = 读端， fds[1] = 写端
        int fds[PIPEFDSIZE]{};
        if(pipe(fds) == -1) {
            perror("pipe");
            exit(1);
        } 
        // 创建子进程后会继承父进程父进程的文件描述符表
        pid_t id = fork();
        if(-1 == id) {
            perror("fork");
            exit(1);
        }
        if(0 == id) {
            // child process
            // 作为读端，需要关闭继承下来的写端
            close(fds[1]);
            // 下面判断是干什么的？
            // 先说没有这三行时会发生什么
            // 当第一次创建子进程时子进程会拷贝(继承)父进程的
            // 文件描述符表，前三个默认打开的不考虑
            // 创建管道后父子进程各自表中的34文件描述符号分别是
            // 管道的读端和写端，然后父进程关闭3号读端
            // 子进程关闭4号写端，此时第一个管道是符合单向通信的
            // 当第二次创建子进程时 子进程继承下来的文件描述符表
            // 中会包含指向上一个管道的写端也就是4号描述符
            // 这样第一个管道就不再是单向通信了，第二个子进程也有能力
            // 给上一个管道写入数据
            // 同理，之后创建的每一个子进程都会出现上述情况
            // 第i次创建子进程，i > 0，那么它的表中就会继承前i - 1个
            // 管道读端的文件描述符
            // 这样就会造成当想依次关闭写端让子进程读到0然后退出
            // 等待回收子进程时造成阻塞(关闭一个紧接着就等待的情况)
            // 阻塞的原因就在于读端不止一个，所以关闭其中一个读端只会让
            // 对应管道的引用计数--，不减到0是不会关闭的，既然关闭不了
            // 子进程就认为可能还会有数据，所以子进程会阻塞等待数据
            // 而子进程阻塞中不退出，自然就无法回收子进程所以wait等待也是阻塞

            // 为了解决上述情况，就需要特殊处理
            // 第i次创建子进程时关闭上一次继承下来的i - 1个文件描述符
            if(i > 0) {
                printf("第%d次创建子进程需要关闭：", i + 1);    
                for(int j = 0; j < i; ++j) {
                    printf("%d ", fds[1] - 1 - j);
                    close(fds[1] - 1 - j);
                }
                cout << endl;
            }
            // 可以传递读端fd
            // 但是为了方便，使用dup2进行输入重定向
            // 将读端重定向到标准输入也就是0号文件描述符
            if(dup2(fds[0], 0) == -1) {
                perror("dup2");
                exit(1);
            }
            // 重定向完毕后当前读端可以关闭了
            close(fds[0]);
            Worker();
            cout << "child proces quit id = " << getpid() << endl;
            exit(0);
        }
        // father process
        // 关闭读端
        close(fds[0]);
        string proName = "process" + to_string(i);
        ppc->push(chunnels(fds[1], id, proName));
        sleep(1);
    }
}


// 通信
void ConnectionOfChunnels(priority_queue<chunnels, vector<chunnels>, typename chunnels::mycmp>* const pc) {
    int taskIdx = 0;
    int cnt = 5;
    while(cnt--) {
        auto ch = pc->top();
        pc->pop();
        ch._cnt++;
        write(ch._fd, &taskIdx, sizeof taskIdx);
        cout << "father says that taslIdx = " << taskIdx << endl;
        pc->push(ch);
        taskIdx = (taskIdx + 1) % vtasks.size();
        sleep(1);
    }
}

void QuitProcess(priority_queue<chunnels, vector<chunnels>, typename chunnels::mycmp>* const pc) {
    // 关闭的同时等待
    while(!pc->empty()) {
        auto ch = pc->top();
        pc->pop();
        close(ch._fd);
        pid_t pid = waitpid(ch._fd, nullptr, 0);
        if(pid != -1) {
            cout << "child quit id = " << pid << endl;
        }
    }
    // 如果在创建时没有依次关闭继承下来的之前管道的写端时
    // 上述写法会阻塞
    // 需要分两次处理，先关闭再等待
    sleep(5);
}

int main() {
    LoadTasks();
    // 父进程作为写端，子进程们作为读端
    // 1. 初始化进程池
    priority_queue<chunnels, vector<chunnels>, typename chunnels::mycmp> pc;
    InitProcessPool(&pc);

    // 2. 父子进程通信
    ConnectionOfChunnels(&pc);
    // 3. 清理资源
    QuitProcess(&pc);
    return 0;
}